﻿// COPYRIGHT 2012, Justin Michalczak
//
// This file is licenced to the public under the Apache
// License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

using System;
using System.Collections.Generic;
using System.Linq;

using CopperVineSoftware.Stackr.Parser.Lexemes;

namespace CopperVineSoftware.Stackr.Compiler.DCPU16Assembly
{
    /// <summary>Compiles definitions into DCPU-16 assembly.</summary>
    class DefinitionCompiler
    {
        private readonly StatementCompiler statementCompiler;

        /// <summary>Constructor.</summary>
        /// <param name="statementCompiler">Statement compiler to use.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="statementCompiler"/> is <c>null</c>.</exception>
        public DefinitionCompiler(StatementCompiler statementCompiler)
        {
            if (statementCompiler == null)
                throw new ArgumentNullException("statementCompiler");

            this.statementCompiler = statementCompiler;
        }

        /// <summary>Compiles a sequence of definitions.</summary>
        /// <param name="definitions">Definitions to compile.</param>
        /// <returns>The compiled instructions for the definitions.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="definitions"/> is <c>null</c>.</exception>
        public IEnumerable<string> Compile(IEnumerable<Definition> definitions)
        {
            if (definitions == null)
                throw new ArgumentNullException("definitions");

            IEnumerable<string> instructions = Enumerable.Empty<string>();
            foreach (Definition definition in definitions)
                instructions = instructions.Concat(this.Compile(definition));

            return instructions;
        }

        /// <summary>Compiles a definition.</summary>
        /// <param name="definition">Definition to compile.</param>
        /// <returns>The compiled instructions for the definition.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="definition"/> is <c>null</c>.</exception>
        public IEnumerable<string> Compile(Definition definition)
        {
            if (definition == null)
                throw new ArgumentNullException("definition");

            if (definition is FunctionDefinition)
                return this.Compile(definition as FunctionDefinition);
            else if (definition is BuiltInFunctionDefinition)
                return this.Compile(definition as BuiltInFunctionDefinition);
            else if (definition is IntegerConstantDefinition)
                return Enumerable.Empty<string>();
            else
                throw new NotImplementedException(string.Format("Definition type ({0}) not implemented.", definition.GetType().Name));
        }

        /// <summary>Compiles a function definition.</summary>
        /// <param name="functionDefinition">Definition to compile.</param>
        /// <returns>The compiled instructions for the definition.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="functionDefinition"/> is <c>null</c>.</exception>
        public IEnumerable<string> Compile(FunctionDefinition functionDefinition)
        {
            if (functionDefinition == null)
                throw new ArgumentNullException("functionDefinition");

            this.statementCompiler.BlockName = functionDefinition.Name;
            yield return string.Format(":{0}", functionDefinition.Name);
            foreach (string instruction in this.statementCompiler.Compile(functionDefinition.Body))
                yield return instruction;
            yield return "SET PC, return";
        }

        /// <summary>Compiles a built-in function definition.</summary>
        /// <param name="builtInDefinition">Definition to compile.</param>
        /// <returns>The compiled instructions for the definition.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="builtInDefinition"/> is <c>null</c>.</exception>
        public IEnumerable<string> Compile(BuiltInFunctionDefinition builtInDefinition)
        {
            if (builtInDefinition == null)
                throw new ArgumentNullException("builtInDefinition");

            yield return string.Format(":{0}", builtInDefinition.Name);
            foreach (string instruction in builtInDefinition.Instructions)
                yield return instruction;
        }

        /// <summary>Compiles an integer constant definition.</summary>
        /// <param name="integerConstantDefinition">Definition to compile.</param>
        /// <returns>The compiled instructions for the definition.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="integerConstantDefinition"/> is <c>null</c>.</exception>
        public IEnumerable<string> Compile(IntegerConstantDefinition integerConstantDefinition)
        {
            if (integerConstantDefinition == null)
                throw new ArgumentNullException("integerConstantDefinition");

            // Nothing to do here; integer constants a compiler-level construct only.
            return Enumerable.Empty<string>();
        }
    }
}
